/***
 *
 *  ipv4/route.c - real-time routing
 *
 *  Copyright (C) 2004, 2005 Jan Kiszka <jan.kiszka@web.de>
 *
 *  Rewritten version of the original route by David Schleef and Ulrich Marx
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <linux/moduleparam.h>
#include <net/ip.h>

#include <rtnet_internal.h>
#include <rtnet_port.h>
#include <rtnet_chrdev.h>
#include <ipv4/af_inet.h>
#include <ipv4/route.h>

/* FIXME: should also become some tunable parameter */
#define ROUTER_FORWARD_PRIO                                                    \
	RTSKB_PRIO_VALUE(QUEUE_MAX_PRIO +                                      \
				 (QUEUE_MIN_PRIO - QUEUE_MAX_PRIO + 1) / 2,    \
			 RTSKB_DEF_RT_CHANNEL)

/* First-level routing: explicite host routes */
struct host_route {
	struct host_route *next;
	struct dest_route dest_host;
};

/* Second-level routing: routes to other networks */
struct net_route {
	struct net_route *next;
	u32 dest_net_ip;
	u32 dest_net_mask;
	u32 gw_ip;
};

#if (CONFIG_XENO_DRIVERS_NET_RTIPV4_HOST_ROUTES &                              \
     (CONFIG_XENO_DRIVERS_NET_RTIPV4_HOST_ROUTES - 1))
#error CONFIG_XENO_DRIVERS_NET_RTIPV4_HOST_ROUTES must be power of 2
#endif
#if CONFIG_XENO_DRIVERS_NET_RTIPV4_HOST_ROUTES < 256
#define HOST_HASH_TBL_SIZE 64
#else
#define HOST_HASH_TBL_SIZE                                                     \
	((CONFIG_XENO_DRIVERS_NET_RTIPV4_HOST_ROUTES / 256) * 64)
#endif
#define HOST_HASH_KEY_MASK (HOST_HASH_TBL_SIZE - 1)

static struct host_route host_routes[CONFIG_XENO_DRIVERS_NET_RTIPV4_HOST_ROUTES];
static struct host_route *free_host_route;
static int allocated_host_routes;
static struct host_route *host_hash_tbl[HOST_HASH_TBL_SIZE];
static DEFINE_RTDM_LOCK(host_table_lock);

#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING
#if (CONFIG_XENO_DRIVERS_NET_RTIPV4_NET_ROUTES &                               \
     (CONFIG_XENO_DRIVERS_NET_RTIPV4_NET_ROUTES - 1))
#error CONFIG_XENO_DRIVERS_NET_RTIPV4_NET_ROUTES must be power of 2
#endif
#if CONFIG_XENO_DRIVERS_NET_RTIPV4_NET_ROUTES < 256
#define NET_HASH_TBL_SIZE 64
#else
#define NET_HASH_TBL_SIZE                                                      \
	((CONFIG_XENO_DRIVERS_NET_RTIPV4_NET_ROUTES / 256) * 64)
#endif
#define NET_HASH_KEY_MASK (NET_HASH_TBL_SIZE - 1)
#define NET_HASH_KEY_SHIFT 8

static struct net_route net_routes[CONFIG_XENO_DRIVERS_NET_RTIPV4_NET_ROUTES];
static struct net_route *free_net_route;
static int allocated_net_routes;
static struct net_route *net_hash_tbl[NET_HASH_TBL_SIZE + 1];
static unsigned int net_hash_key_shift = NET_HASH_KEY_SHIFT;
static DEFINE_RTDM_LOCK(net_table_lock);

module_param(net_hash_key_shift, uint, 0444);
MODULE_PARM_DESC(net_hash_key_shift, "destination right shift for "
				     "network hash key (default: 8)");
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING */

/***
 *  proc filesystem section
 */
#ifdef CONFIG_XENO_OPT_VFILE
static int rtnet_ipv4_route_show(struct xnvfile_regular_iterator *it, void *d)
{
#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING
	u32 mask;
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING */

	xnvfile_printf(it,
		       "Host routes allocated/total:\t%d/%d\n"
		       "Host hash table size:\t\t%d\n",
		       allocated_host_routes,
		       CONFIG_XENO_DRIVERS_NET_RTIPV4_HOST_ROUTES,
		       HOST_HASH_TBL_SIZE);

#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING
	mask = NET_HASH_KEY_MASK << net_hash_key_shift;
	xnvfile_printf(it,
		       "Network routes allocated/total:\t%d/%d\n"
		       "Network hash table size:\t%d\n"
		       "Network hash key shift/mask:\t%d/%08X\n",
		       allocated_net_routes,
		       CONFIG_XENO_DRIVERS_NET_RTIPV4_NET_ROUTES,
		       NET_HASH_TBL_SIZE, net_hash_key_shift, mask);
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING */

#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_ROUTER
	xnvfile_printf(it, "IP Router:\t\t\tyes\n");
#else
	xnvfile_printf(it, "IP Router:\t\t\tno\n");
#endif

	return 0;
}

static int rtnet_ipv4_module_lock(struct xnvfile *vfile)
{
	bool res = try_module_get(THIS_MODULE);
	if (!res)
		return -EIDRM;

	return 0;
}

static void rtnet_ipv4_module_unlock(struct xnvfile *vfile)
{
	module_put(THIS_MODULE);
}

static struct xnvfile_lock_ops rtnet_ipv4_module_lock_ops = {
	.get = rtnet_ipv4_module_lock,
	.put = rtnet_ipv4_module_unlock,
};

static struct xnvfile_regular_ops rtnet_ipv4_route_vfile_ops = {
	.show = rtnet_ipv4_route_show,
};

static struct xnvfile_regular rtnet_ipv4_route_vfile = {
    .entry = {
	.lockops = &rtnet_ipv4_module_lock_ops,
    },
    .ops = &rtnet_ipv4_route_vfile_ops,
};

static rtdm_lockctx_t rtnet_ipv4_host_route_lock_ctx;

static int rtnet_ipv4_host_route_lock(struct xnvfile *vfile)
{
	rtdm_lock_get_irqsave(&host_table_lock, rtnet_ipv4_host_route_lock_ctx);
	return 0;
}

static void rtnet_ipv4_host_route_unlock(struct xnvfile *vfile)
{
	rtdm_lock_put_irqrestore(&host_table_lock,
				 rtnet_ipv4_host_route_lock_ctx);
}

static struct xnvfile_lock_ops rtnet_ipv4_host_route_lock_ops = {
	.get = rtnet_ipv4_host_route_lock,
	.put = rtnet_ipv4_host_route_unlock,
};

struct rtnet_ipv4_host_route_priv {
	unsigned key;
	struct host_route *entry_ptr;
};

struct rtnet_ipv4_host_route_data {
	int key;
	char name[IFNAMSIZ];
	struct dest_route dest_host;
};

struct xnvfile_rev_tag host_route_tag;

static void *rtnet_ipv4_host_route_begin(struct xnvfile_snapshot_iterator *it)
{
	struct rtnet_ipv4_host_route_priv *priv = xnvfile_iterator_priv(it);
	struct rtnet_ipv4_host_route_data *data;
	unsigned routes;
	int err;

	routes = allocated_host_routes;
	if (!routes)
		return VFILE_SEQ_EMPTY;

	data = kmalloc(sizeof(*data) * routes, GFP_KERNEL);
	if (data == NULL)
		return NULL;

	err = rtnet_ipv4_module_lock(NULL);
	if (err < 0) {
		kfree(data);
		return VFILE_SEQ_EMPTY;
	}

	priv->key = -1;
	priv->entry_ptr = NULL;
	return data;
}

static void rtnet_ipv4_host_route_end(struct xnvfile_snapshot_iterator *it,
				      void *buf)
{
	rtnet_ipv4_module_unlock(NULL);
	kfree(buf);
}

static int rtnet_ipv4_host_route_next(struct xnvfile_snapshot_iterator *it,
				      void *data)
{
	struct rtnet_ipv4_host_route_priv *priv = xnvfile_iterator_priv(it);
	struct rtnet_ipv4_host_route_data *p = data;
	struct rtnet_device *rtdev;

	if (priv->entry_ptr == NULL) {
		if (++priv->key >= HOST_HASH_TBL_SIZE)
			return 0;

		priv->entry_ptr = host_hash_tbl[priv->key];
		if (priv->entry_ptr == NULL)
			return VFILE_SEQ_SKIP;
	}

	rtdev = priv->entry_ptr->dest_host.rtdev;

	if (!rtdev_reference(rtdev))
		return -EIDRM;

	memcpy(&p->name, rtdev->name, sizeof(p->name));

	rtdev_dereference(rtdev);

	p->key = priv->key;

	memcpy(&p->dest_host, &priv->entry_ptr->dest_host,
	       sizeof(p->dest_host));

	priv->entry_ptr = priv->entry_ptr->next;

	return 1;
}

static int rtnet_ipv4_host_route_show(struct xnvfile_snapshot_iterator *it,
				      void *data)
{
	struct rtnet_ipv4_host_route_data *p = data;

	if (p == NULL) {
		xnvfile_printf(it, "Hash\tDestination\tHW Address\t\tDevice\n");
		return 0;
	}

	xnvfile_printf(it,
		       "%02X\t%u.%u.%u.%-3u\t"
		       "%02X:%02X:%02X:%02X:%02X:%02X\t%s\n",
		       p->key, NIPQUAD(p->dest_host.ip),
		       p->dest_host.dev_addr[0], p->dest_host.dev_addr[1],
		       p->dest_host.dev_addr[2], p->dest_host.dev_addr[3],
		       p->dest_host.dev_addr[4], p->dest_host.dev_addr[5],
		       p->name);
	return 0;
}

static struct xnvfile_snapshot_ops rtnet_ipv4_host_route_vfile_ops = {
	.begin = rtnet_ipv4_host_route_begin,
	.end = rtnet_ipv4_host_route_end,
	.next = rtnet_ipv4_host_route_next,
	.show = rtnet_ipv4_host_route_show,
};

static struct xnvfile_snapshot rtnet_ipv4_host_route_vfile = {
    .entry = {
	.lockops = &rtnet_ipv4_host_route_lock_ops,
    },
    .privsz = sizeof(struct rtnet_ipv4_host_route_priv),
    .datasz = sizeof(struct rtnet_ipv4_host_route_data),
    .tag = &host_route_tag,
    .ops = &rtnet_ipv4_host_route_vfile_ops,
};

static struct xnvfile_link rtnet_ipv4_arp_vfile;

#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING
static rtdm_lockctx_t rtnet_ipv4_net_route_lock_ctx;

static int rtnet_ipv4_net_route_lock(struct xnvfile *vfile)
{
	rtdm_lock_get_irqsave(&net_table_lock, rtnet_ipv4_net_route_lock_ctx);
	return 0;
}

static void rtnet_ipv4_net_route_unlock(struct xnvfile *vfile)
{
	rtdm_lock_put_irqrestore(&net_table_lock,
				 rtnet_ipv4_net_route_lock_ctx);
}

static struct xnvfile_lock_ops rtnet_ipv4_net_route_lock_ops = {
	.get = rtnet_ipv4_net_route_lock,
	.put = rtnet_ipv4_net_route_unlock,
};

struct rtnet_ipv4_net_route_priv {
	unsigned key;
	struct net_route *entry_ptr;
};

struct rtnet_ipv4_net_route_data {
	int key;
	u32 dest_net_ip;
	u32 dest_net_mask;
	u32 gw_ip;
};

struct xnvfile_rev_tag net_route_tag;

static void *rtnet_ipv4_net_route_begin(struct xnvfile_snapshot_iterator *it)
{
	struct rtnet_ipv4_net_route_priv *priv = xnvfile_iterator_priv(it);
	struct rtnet_ipv4_net_route_data *data;
	unsigned routes;
	int err;

	routes = allocated_net_routes;
	if (!routes)
		return VFILE_SEQ_EMPTY;

	data = kmalloc(sizeof(*data) * routes, GFP_KERNEL);
	if (data == NULL)
		return NULL;

	err = rtnet_ipv4_module_lock(NULL);
	if (err < 0) {
		kfree(data);
		return VFILE_SEQ_EMPTY;
	}

	priv->key = -1;
	priv->entry_ptr = NULL;
	return data;
}

static void rtnet_ipv4_net_route_end(struct xnvfile_snapshot_iterator *it,
				     void *buf)
{
	rtnet_ipv4_module_unlock(NULL);
	kfree(buf);
}

static int rtnet_ipv4_net_route_next(struct xnvfile_snapshot_iterator *it,
				     void *data)
{
	struct rtnet_ipv4_net_route_priv *priv = xnvfile_iterator_priv(it);
	struct rtnet_ipv4_net_route_data *p = data;

	if (priv->entry_ptr == NULL) {
		if (++priv->key >= NET_HASH_TBL_SIZE + 1)
			return 0;

		priv->entry_ptr = net_hash_tbl[priv->key];
		if (priv->entry_ptr == NULL)
			return VFILE_SEQ_SKIP;
	}

	p->key = priv->key;
	p->dest_net_ip = priv->entry_ptr->dest_net_ip;
	p->dest_net_mask = priv->entry_ptr->dest_net_mask;
	p->gw_ip = priv->entry_ptr->gw_ip;

	priv->entry_ptr = priv->entry_ptr->next;

	return 1;
}

static int rtnet_ipv4_net_route_show(struct xnvfile_snapshot_iterator *it,
				     void *data)
{
	struct rtnet_ipv4_net_route_data *p = data;

	if (p == NULL) {
		xnvfile_printf(it, "Hash\tDestination\tMask\t\t\tGateway\n");
		return 0;
	}

	if (p->key < NET_HASH_TBL_SIZE)
		xnvfile_printf(it,
			       "%02X\t%u.%u.%u.%-3u\t%u.%u.%u.%-3u"
			       "\t\t%u.%u.%u.%-3u\n",
			       p->key, NIPQUAD(p->dest_net_ip),
			       NIPQUAD(p->dest_net_mask), NIPQUAD(p->gw_ip));
	else
		xnvfile_printf(it,
			       "*\t%u.%u.%u.%-3u\t%u.%u.%u.%-3u\t\t"
			       "%u.%u.%u.%-3u\n",
			       NIPQUAD(p->dest_net_ip),
			       NIPQUAD(p->dest_net_mask), NIPQUAD(p->gw_ip));

	return 0;
}

static struct xnvfile_snapshot_ops rtnet_ipv4_net_route_vfile_ops = {
	.begin = rtnet_ipv4_net_route_begin,
	.end = rtnet_ipv4_net_route_end,
	.next = rtnet_ipv4_net_route_next,
	.show = rtnet_ipv4_net_route_show,
};

static struct xnvfile_snapshot rtnet_ipv4_net_route_vfile = {
    .entry = {
	.lockops = &rtnet_ipv4_net_route_lock_ops,
    },
    .privsz = sizeof(struct rtnet_ipv4_net_route_priv),
    .datasz = sizeof(struct rtnet_ipv4_net_route_data),
    .tag = &net_route_tag,
    .ops = &rtnet_ipv4_net_route_vfile_ops,
};
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING */

static int __init rt_route_proc_register(void)
{
	int err;

	err = xnvfile_init_regular("route", &rtnet_ipv4_route_vfile,
				   &ipv4_proc_root);
	if (err < 0)
		goto err1;

	err = xnvfile_init_snapshot("host_route", &rtnet_ipv4_host_route_vfile,
				    &ipv4_proc_root);
	if (err < 0)
		goto err2;

	/* create "arp" as an alias for "host_route" */
	err = xnvfile_init_link("arp", "host_route", &rtnet_ipv4_arp_vfile,
				&ipv4_proc_root);
	if (err < 0)
		goto err3;

#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING
	err = xnvfile_init_snapshot("net_route", &rtnet_ipv4_net_route_vfile,
				    &ipv4_proc_root);
	if (err < 0)
		goto err4;
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING */

	return 0;

#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING
err4:
	xnvfile_destroy_link(&rtnet_ipv4_arp_vfile);
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING */

err3:
	xnvfile_destroy_snapshot(&rtnet_ipv4_host_route_vfile);

err2:
	xnvfile_destroy_regular(&rtnet_ipv4_route_vfile);

err1:
	printk("RTnet: unable to initialize /proc entries (route)\n");
	return err;
}

static void rt_route_proc_unregister(void)
{
#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING
	xnvfile_destroy_snapshot(&rtnet_ipv4_net_route_vfile);
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING */
	xnvfile_destroy_link(&rtnet_ipv4_arp_vfile);
	xnvfile_destroy_snapshot(&rtnet_ipv4_host_route_vfile);
	xnvfile_destroy_regular(&rtnet_ipv4_route_vfile);
}
#endif /* CONFIG_XENO_OPT_VFILE */

/***
 *  rt_alloc_host_route - allocates new host route
 */
static inline struct host_route *rt_alloc_host_route(void)
{
	rtdm_lockctx_t context;
	struct host_route *rt;

	rtdm_lock_get_irqsave(&host_table_lock, context);

	if ((rt = free_host_route) != NULL) {
		free_host_route = rt->next;
		allocated_host_routes++;
	}

	rtdm_lock_put_irqrestore(&host_table_lock, context);

	return rt;
}

/***
 *  rt_free_host_route - releases host route
 *
 *  Note: must be called with host_table_lock held
 */
static inline void rt_free_host_route(struct host_route *rt)
{
	rt->next = free_host_route;
	free_host_route = rt;
	allocated_host_routes--;
}

/***
 *  rt_ip_route_add_host: add or update host route
 */
int rt_ip_route_add_host(u32 addr, unsigned char *dev_addr,
			 struct rtnet_device *rtdev)
{
	rtdm_lockctx_t context;
	struct host_route *new_route;
	struct host_route *rt;
	unsigned int key;
	int ret = 0;

	rtdm_lock_get_irqsave(&rtdev->rtdev_lock, context);

	if ((!test_bit(PRIV_FLAG_UP, &rtdev->priv_flags) ||
	     test_and_set_bit(PRIV_FLAG_ADDING_ROUTE, &rtdev->priv_flags))) {
		rtdm_lock_put_irqrestore(&rtdev->rtdev_lock, context);
		return -EBUSY;
	}

	rtdm_lock_put_irqrestore(&rtdev->rtdev_lock, context);

	if ((new_route = rt_alloc_host_route()) != NULL) {
		new_route->dest_host.ip = addr;
		new_route->dest_host.rtdev = rtdev;
		memcpy(new_route->dest_host.dev_addr, dev_addr,
		       rtdev->addr_len);
	}

	key = ntohl(addr) & HOST_HASH_KEY_MASK;

	rtdm_lock_get_irqsave(&host_table_lock, context);

	xnvfile_touch_tag(&host_route_tag);

	rt = host_hash_tbl[key];
	while (rt != NULL) {
		if ((rt->dest_host.ip == addr) &&
		    (rt->dest_host.rtdev->local_ip == rtdev->local_ip)) {
			rt->dest_host.rtdev = rtdev;
			memcpy(rt->dest_host.dev_addr, dev_addr,
			       rtdev->addr_len);

			if (new_route)
				rt_free_host_route(new_route);

			rtdm_lock_put_irqrestore(&host_table_lock, context);

			goto out;
		}

		rt = rt->next;
	}

	if (new_route) {
		new_route->next = host_hash_tbl[key];
		host_hash_tbl[key] = new_route;

		rtdm_lock_put_irqrestore(&host_table_lock, context);
	} else {
		rtdm_lock_put_irqrestore(&host_table_lock, context);

		/*ERRMSG*/ rtdm_printk(
			"RTnet: no more host routes available\n");
		ret = -ENOBUFS;
	}

out:
	clear_bit(PRIV_FLAG_ADDING_ROUTE, &rtdev->priv_flags);

	return ret;
}

/***
 *  rt_ip_route_del_host - deletes specified host route
 */
int rt_ip_route_del_host(u32 addr, struct rtnet_device *rtdev)
{
	rtdm_lockctx_t context;
	struct host_route *rt;
	struct host_route **last_ptr;
	unsigned int key;

	key = ntohl(addr) & HOST_HASH_KEY_MASK;
	last_ptr = &host_hash_tbl[key];

	rtdm_lock_get_irqsave(&host_table_lock, context);

	rt = host_hash_tbl[key];
	while (rt != NULL) {
		if ((rt->dest_host.ip == addr) &&
		    (!rtdev ||
		     (rt->dest_host.rtdev->local_ip == rtdev->local_ip))) {
			*last_ptr = rt->next;

			rt_free_host_route(rt);

			xnvfile_touch_tag(&host_route_tag);

			rtdm_lock_put_irqrestore(&host_table_lock, context);

			return 0;
		}

		last_ptr = &rt->next;
		rt = rt->next;
	}

	rtdm_lock_put_irqrestore(&host_table_lock, context);

	return -ENOENT;
}

/***
 *  rt_ip_route_del_all - deletes all routes associated with a specified device
 */
void rt_ip_route_del_all(struct rtnet_device *rtdev)
{
	rtdm_lockctx_t context;
	struct host_route *host_rt;
	struct host_route **last_host_ptr;
	unsigned int key;
	u32 ip;

	for (key = 0; key < HOST_HASH_TBL_SIZE; key++) {
	host_start_over:
		last_host_ptr = &host_hash_tbl[key];

		rtdm_lock_get_irqsave(&host_table_lock, context);

		host_rt = host_hash_tbl[key];
		while (host_rt != NULL) {
			if (host_rt->dest_host.rtdev == rtdev) {
				*last_host_ptr = host_rt->next;

				rt_free_host_route(host_rt);

				rtdm_lock_put_irqrestore(&host_table_lock,
							 context);

				goto host_start_over;
			}

			last_host_ptr = &host_rt->next;
			host_rt = host_rt->next;
		}

		rtdm_lock_put_irqrestore(&host_table_lock, context);
	}

	if ((ip = rtdev->local_ip) != 0)
		rt_ip_route_del_host(ip, rtdev);
}

/***
 *  rt_ip_route_get_host - check if specified host route is resolved
 */
int rt_ip_route_get_host(u32 addr, char *if_name, unsigned char *dev_addr,
			 struct rtnet_device *rtdev)
{
	rtdm_lockctx_t context;
	struct host_route *rt;
	unsigned int key;

	key = ntohl(addr) & HOST_HASH_KEY_MASK;

	rtdm_lock_get_irqsave(&host_table_lock, context);

	rt = host_hash_tbl[key];
	while (rt != NULL) {
		if ((rt->dest_host.ip == addr) &&
		    (!rtdev ||
		     rt->dest_host.rtdev->local_ip == rtdev->local_ip)) {
			memcpy(dev_addr, rt->dest_host.dev_addr,
			       rt->dest_host.rtdev->addr_len);
			strncpy(if_name, rt->dest_host.rtdev->name, IFNAMSIZ);

			rtdm_lock_put_irqrestore(&host_table_lock, context);
			return 0;
		}

		rt = rt->next;
	}

	rtdm_lock_put_irqrestore(&host_table_lock, context);

	return -ENOENT;
}

#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING
/***
 *  rt_alloc_net_route - allocates new network route
 */
static inline struct net_route *rt_alloc_net_route(void)
{
	rtdm_lockctx_t context;
	struct net_route *rt;

	rtdm_lock_get_irqsave(&net_table_lock, context);

	if ((rt = free_net_route) != NULL) {
		free_net_route = rt->next;
		allocated_net_routes++;
	}

	rtdm_lock_put_irqrestore(&net_table_lock, context);

	return rt;
}

/***
 *  rt_free_net_route - releases network route
 *
 *  Note: must be called with net_table_lock held
 */
static inline void rt_free_net_route(struct net_route *rt)
{
	rt->next = free_net_route;
	free_net_route = rt;
	allocated_host_routes--;
}

/***
 *  rt_ip_route_add_net: add or update network route
 */
int rt_ip_route_add_net(u32 addr, u32 mask, u32 gw_addr)
{
	rtdm_lockctx_t context;
	struct net_route *new_route;
	struct net_route *rt;
	struct net_route **last_ptr;
	unsigned int key;
	u32 shifted_mask;

	addr &= mask;

	if ((new_route = rt_alloc_net_route()) != NULL) {
		new_route->dest_net_ip = addr;
		new_route->dest_net_mask = mask;
		new_route->gw_ip = gw_addr;
	}

	shifted_mask = NET_HASH_KEY_MASK << net_hash_key_shift;
	if ((mask & shifted_mask) == shifted_mask)
		key = (ntohl(addr) >> net_hash_key_shift) & NET_HASH_KEY_MASK;
	else
		key = NET_HASH_TBL_SIZE;
	last_ptr = &net_hash_tbl[key];

	rtdm_lock_get_irqsave(&net_table_lock, context);

	xnvfile_touch_tag(&net_route_tag);

	rt = net_hash_tbl[key];
	while (rt != NULL) {
		if ((rt->dest_net_ip == addr) && (rt->dest_net_mask == mask)) {
			rt->gw_ip = gw_addr;

			if (new_route)
				rt_free_net_route(new_route);

			rtdm_lock_put_irqrestore(&net_table_lock, context);

			return 0;
		}

		last_ptr = &rt->next;
		rt = rt->next;
	}

	if (new_route) {
		new_route->next = *last_ptr;
		*last_ptr = new_route;

		rtdm_lock_put_irqrestore(&net_table_lock, context);

		return 0;
	} else {
		rtdm_lock_put_irqrestore(&net_table_lock, context);

		/*ERRMSG*/ rtdm_printk(
			"RTnet: no more network routes available\n");
		return -ENOBUFS;
	}
}

/***
 *  rt_ip_route_del_net - deletes specified network route
 */
int rt_ip_route_del_net(u32 addr, u32 mask)
{
	rtdm_lockctx_t context;
	struct net_route *rt;
	struct net_route **last_ptr;
	unsigned int key;
	u32 shifted_mask;

	addr &= mask;

	shifted_mask = NET_HASH_KEY_MASK << net_hash_key_shift;
	if ((mask & shifted_mask) == shifted_mask)
		key = (ntohl(addr) >> net_hash_key_shift) & NET_HASH_KEY_MASK;
	else
		key = NET_HASH_TBL_SIZE;
	last_ptr = &net_hash_tbl[key];

	rtdm_lock_get_irqsave(&net_table_lock, context);

	rt = net_hash_tbl[key];
	while (rt != NULL) {
		if ((rt->dest_net_ip == addr) && (rt->dest_net_mask == mask)) {
			*last_ptr = rt->next;

			rt_free_net_route(rt);

			xnvfile_touch_tag(&net_route_tag);

			rtdm_lock_put_irqrestore(&net_table_lock, context);

			return 0;
		}

		last_ptr = &rt->next;
		rt = rt->next;
	}

	rtdm_lock_put_irqrestore(&net_table_lock, context);

	return -ENOENT;
}
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING */

/***
 *  rt_ip_route_output - looks up output route
 *
 *  Note: increments refcount on returned rtdev in rt_buf
 */
int rt_ip_route_output(struct dest_route *rt_buf, u32 daddr, u32 saddr)
{
	rtdm_lockctx_t context;
	struct host_route *host_rt;
	unsigned int key;

#ifndef CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING
#define DADDR daddr
#else
#define DADDR real_daddr

	struct net_route *net_rt;
	int lookup_gw = 1;
	u32 real_daddr = daddr;

restart:
#endif /* !CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING */

	key = ntohl(daddr) & HOST_HASH_KEY_MASK;

	rtdm_lock_get_irqsave(&host_table_lock, context);

	host_rt = host_hash_tbl[key];
	if (likely(saddr == INADDR_ANY))
		while (host_rt != NULL) {
			if (host_rt->dest_host.ip == daddr) {
			host_route_found:
				if (!rtdev_reference(
					    host_rt->dest_host.rtdev)) {
					rtdm_lock_put_irqrestore(
						&host_table_lock, context);
					goto next;
				}

				memcpy(rt_buf->dev_addr,
				       &host_rt->dest_host.dev_addr,
				       sizeof(rt_buf->dev_addr));
				rt_buf->rtdev = host_rt->dest_host.rtdev;

				rtdm_lock_put_irqrestore(&host_table_lock,
							 context);

				rt_buf->ip = DADDR;

				return 0;
			}
		next:
			host_rt = host_rt->next;
		}
	else
		while (host_rt != NULL) {
			if ((host_rt->dest_host.ip == daddr) &&
			    (host_rt->dest_host.rtdev->local_ip == saddr))
				goto host_route_found;
			host_rt = host_rt->next;
		}

	rtdm_lock_put_irqrestore(&host_table_lock, context);

#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING
	if (lookup_gw) {
		lookup_gw = 0;
		key = (ntohl(daddr) >> net_hash_key_shift) & NET_HASH_KEY_MASK;

		rtdm_lock_get_irqsave(&net_table_lock, context);

		net_rt = net_hash_tbl[key];
		while (net_rt != NULL) {
			if (net_rt->dest_net_ip ==
			    (daddr & net_rt->dest_net_mask)) {
				daddr = net_rt->gw_ip;

				rtdm_lock_put_irqrestore(&net_table_lock,
							 context);

				/* start over, now using the gateway ip as destination */
				goto restart;
			}

			net_rt = net_rt->next;
		}

		rtdm_lock_put_irqrestore(&net_table_lock, context);

		/* last try: no hash key */
		rtdm_lock_get_irqsave(&net_table_lock, context);

		net_rt = net_hash_tbl[NET_HASH_TBL_SIZE];
		while (net_rt != NULL) {
			if (net_rt->dest_net_ip ==
			    (daddr & net_rt->dest_net_mask)) {
				daddr = net_rt->gw_ip;

				rtdm_lock_put_irqrestore(&net_table_lock,
							 context);

				/* start over, now using the gateway ip as destination */
				goto restart;
			}

			net_rt = net_rt->next;
		}

		rtdm_lock_put_irqrestore(&net_table_lock, context);
	}
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING */

	/*ERRMSG*/ rtdm_printk("RTnet: host %u.%u.%u.%u unreachable\n",
			       NIPQUAD(daddr));
	return -EHOSTUNREACH;
}

#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_ROUTER
int rt_ip_route_forward(struct rtskb *rtskb, u32 daddr)
{
	struct rtnet_device *rtdev = rtskb->rtdev;
	struct dest_route dest;

	if (likely((daddr == rtdev->local_ip) ||
		   (daddr == rtdev->broadcast_ip) ||
		   (rtdev->flags & IFF_LOOPBACK)))
		return 0;

	if (rtskb_acquire(rtskb, &global_pool) != 0) {
		/*ERRMSG*/ rtdm_printk(
			"RTnet: router overloaded, dropping packet\n");
		goto error;
	}

	if (rt_ip_route_output(&dest, daddr, INADDR_ANY) < 0) {
		/*ERRMSG*/ rtdm_printk(
			"RTnet: unable to forward packet from %u.%u.%u.%u\n",
			NIPQUAD(rtskb->nh.iph->saddr));
		goto error;
	}

	rtskb->rtdev = dest.rtdev;
	rtskb->priority = ROUTER_FORWARD_PRIO;

	if ((dest.rtdev->hard_header) &&
	    (dest.rtdev->hard_header(rtskb, dest.rtdev, ETH_P_IP, dest.dev_addr,
				     dest.rtdev->dev_addr, rtskb->len) < 0))
		goto error;

	rtdev_xmit(rtskb);

	return 1;

error:
	kfree_rtskb(rtskb);
	return 1;
}
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4_ROUTER */

/***
 *  rt_ip_routing_init: initialize
 */
int __init rt_ip_routing_init(void)
{
	int i;

	for (i = 0; i < CONFIG_XENO_DRIVERS_NET_RTIPV4_HOST_ROUTES - 2; i++)
		host_routes[i].next = &host_routes[i + 1];
	free_host_route = &host_routes[0];

#ifdef CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING
	for (i = 0; i < CONFIG_XENO_DRIVERS_NET_RTIPV4_NET_ROUTES - 2; i++)
		net_routes[i].next = &net_routes[i + 1];
	free_net_route = &net_routes[0];
#endif /* CONFIG_XENO_DRIVERS_NET_RTIPV4_NETROUTING */

#ifdef CONFIG_XENO_OPT_VFILE
	return rt_route_proc_register();
#else /* !CONFIG_XENO_OPT_VFILE */
	return 0;
#endif /* CONFIG_XENO_OPT_VFILE */
}

/***
 *  rt_ip_routing_realease
 */
void rt_ip_routing_release(void)
{
#ifdef CONFIG_XENO_OPT_VFILE
	rt_route_proc_unregister();
#endif /* CONFIG_XENO_OPT_VFILE */
}

EXPORT_SYMBOL_GPL(rt_ip_route_add_host);
EXPORT_SYMBOL_GPL(rt_ip_route_del_host);
EXPORT_SYMBOL_GPL(rt_ip_route_del_all);
EXPORT_SYMBOL_GPL(rt_ip_route_output);
